﻿namespace Frameworks
{
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using UnityEngine;

    internal class LoggerAppWindow : MonoBehaviour
    {
        private static readonly Rect DefaultIconRect = new Rect(10f, 10f, 80f, 30f);
        private static readonly Rect DefaultWindowRect = new Rect(10f, 10f, 512f, 720f);
        //private static readonly int DefaultFontSize = (int)(Screen.height * 0.02f);
        private static readonly int DefaultFontSize = 20;

        private Rect _dragRect = new Rect(24f, 0f, float.MaxValue, 24f);
        private Rect _iconRect = DefaultIconRect;
        private Rect _windowRect = DefaultWindowRect;

        private FpsCounter _fpsCounter;
        public Color _guiWindowColor = new Color(1, 1, 1, 0.5f);
        public Color _guiContentColor = new Color(1, 1, 1, 0.75f);
        private GUIStyle _logWindowStyle;
        private GUIStyle _toolbarStyle;
        private GUIStyle _logLineStyle1;
        private GUIStyle _logLineStyle2;
        private GUIStyle _selectedLogLineStyle;
        private Texture2D _smallErrorIcon;
        private Texture2D _smallWarningIcon;
        private Texture2D _smallMessageIcon;
        private Texture2D _deleteButton;
        private Texture2D _windowTexture;
        private Texture2D _toolbarButton;
        private Texture2D _toolbarButtonClicked;
        private Texture2D _toolbarButtonDown;
        private Texture2D _logLine1;
        private Texture2D _logLine2;
        private Texture2D _logLineSelected;

        private bool _showWindow = false;
        private bool _showTimes = true;
        private bool _showError = true;
        private bool _showWarning = true;
        private bool _showMessage = true;

        private string _command = "";
        private string _filterRegex = string.Empty;
        private string _currentChannel = string.Empty;

        private List<LogInfo> _logInfoList = new List<LogInfo>();
        private Vector2 _logListScrollPosition;
        private Vector2 _logDetailsScrollPosition;
        private int _NoErrors;
        private int _NoWarnings;
        private int _NoMessages;
        private int _selectedCallstackFrame = 0;
        private int _selectedMessage = -1;
        private double _lastMessageClickTime = 0;
        private static readonly float _mouseClickTime = 0.2f;
        
        private DebugMouse _debugMouse;
        private float _mouseDownTime;

        public void Log(LogInfo logInfo)
        {
            _logInfoList.Add(logInfo);

            if (logInfo.Type == LogType.Error || logInfo.Type == LogType.Exception)
                _NoErrors++;
            else if (logInfo.Type == LogType.Warning)
                _NoWarnings++;
            else
                _NoMessages++;

            TrimOverLimitLogs();
        }

        private void Awake()
        {
            DontDestroyOnLoad(gameObject);
            _fpsCounter = new FpsCounter();
        }
        
        private void Start()
        {
            ClearSelectedMessage();
            //CurrentTopPaneHeight = Screen.height * SizerStartHeightRatio;

            _smallErrorIcon = GenerateIcon.SmallErrorIcon();
            _smallWarningIcon = GenerateIcon.SmallWarningIcon();
            _smallMessageIcon = GenerateIcon.SmallMessageIcon();
            _deleteButton = GenerateIcon.DeleteButton();
            _windowTexture = GenerateIcon.WindowTexture();
            _toolbarButton = GenerateIcon.ToolbarButton();
            _toolbarButtonClicked = GenerateIcon.ToolbarButtonClicked();
            _toolbarButtonDown = GenerateIcon.ToolbarButtonDown();
            _logLine1 = GenerateIcon.LogLine1();
            _logLine2 = GenerateIcon.LogLine2();
            _logLineSelected = GenerateIcon.LogLineSelected();

            _logWindowStyle = GUISkin_LogWindow();
            _toolbarStyle = GUISkin_Toolbar();
            _logLineStyle1 = GUISkin_LogLine(_logLine1);
            _logLineStyle2 = GUISkin_LogLine(_logLine2);
            _selectedLogLineStyle = GUISkin_LogLine(_logLineSelected);
        }

        public void Update()
        {
            _fpsCounter.Update(Time.deltaTime, Time.unscaledDeltaTime);
        }

        public void OnGUI()
        {
            GUISkin cachedGuiSkin = GUI.skin;
            Matrix4x4 cachedMatrix = GUI.matrix;

            //GUI.skin = m_Skin;
            //GUI.matrix = Matrix4x4.Scale(new Vector3(m_WindowScale, m_WindowScale, 1f));
            
            var oldGUIColour = GUI.color;
            GUI.color = _guiContentColor;
            if (_showWindow)
            {
                _windowRect = GUILayout.Window(1, _windowRect, DrawWindow, "<b>DEBUGGER</b>", _logWindowStyle);
            }
            else
            {
                DrawDebuggerWindowIcon();
            }
            GUI.color = oldGUIColour;
            
            GUI.matrix = cachedMatrix;
            GUI.skin = cachedGuiSkin;
        }


        private void DrawWindow(int windowId)
        {
            GUI.DragWindow(_dragRect);
            
            GUILayout.BeginVertical(GUILayout.Height(_windowRect.height - _logWindowStyle.padding.top), GUILayout.MinHeight(100));
            DrawToolbar();
            GUILayout.Space(5);
            DrawCommand();
            //GUILayout.Space(5);
            //DrawFilter();
            //GUILayout.Space(5);
            //DrawChannels();
            GUILayout.Space(2);
            DrawLogList();
            DrawLogStackTrace();
            GUILayout.EndVertical();

            DrawActivationButton();
        }

        private void DrawDebuggerWindowIcon()
        {
            if (_iconRect.Contains(Event.current.mousePosition))
            {
                if (Event.current.type == EventType.MouseDown)
                {
                    _debugMouse = DebugMouse.Down;
                    _mouseDownTime = Time.realtimeSinceStartup;
                }
                else if (Event.current.type == EventType.MouseUp)
                {
                    _debugMouse = DebugMouse.Up;
                    if ((Time.realtimeSinceStartup - _mouseDownTime) < _mouseClickTime)
                    {
                        _showWindow = true;
                    }
                }
            }
            if ((_debugMouse == DebugMouse.Down) && (Event.current.type == EventType.MouseDrag))
            {
                _iconRect.x = Event.current.mousePosition.x - (_iconRect.width / 2f);
                _iconRect.y = Event.current.mousePosition.y - (_iconRect.height / 2f);
            }
            GUI.Button(_iconRect, _fpsCounter.CurrentFps, _toolbarStyle);
        }

        private void DrawToolbar()
        {
            GUILayout.BeginHorizontal();
            if (ButtonClamped("Clear", _toolbarStyle))
            {
                Clear();
            }
            _showTimes = ToggleClamped(_showTimes, "Show Times", _toolbarStyle);

            GUILayout.FlexibleSpace();

            LabelClamped(_fpsCounter.CurrentFps, GetGUISkin(GUI.skin.label, Color.white, TextAnchor.UpperCenter));
            var showError = ToggleClamped(_showError, new GUIContent(_NoErrors.ToString(), _smallErrorIcon), _toolbarStyle);
            var showWarning = ToggleClamped(_showWarning, new GUIContent(_NoWarnings.ToString(), _smallWarningIcon), _toolbarStyle);
            var showMessage = ToggleClamped(_showMessage, new GUIContent(_NoMessages.ToString(), _smallMessageIcon), _toolbarStyle);

            if (showError != _showError || showWarning != _showWarning || showMessage != _showMessage)
            {
                ClearSelectedMessage();
            }
            _showError = showError;
            _showWarning = showWarning;
            _showMessage = showMessage;
            GUILayout.EndHorizontal();
        }

        private void DrawCommand()
        {
            GUILayout.BeginHorizontal();
            LabelClamped("Command", GetGUISkin(GUI.skin.label, Color.white, TextAnchor.MiddleLeft));
            _command = GUILayout.TextField(_command, GetGUISkin(GUI.skin.textField, Color.white, TextAnchor.MiddleLeft));
     
            if (ButtonClamped("Execute", GetGUISkin(GUI.skin.button, Color.white, TextAnchor.MiddleCenter)))
            {
                if (!string.IsNullOrEmpty(_command))
                {
                    if (Debugger.GM_Command != null)
                        Debugger.GM_Command.Invoke(_command);
                }
            }
            GUILayout.EndHorizontal();
        }

        private void DrawFilter()
        {
            GUILayout.BeginHorizontal();
            LabelClamped("Filter Regex", GUI.skin.label);
            var filterRegex = GUILayout.TextArea(_filterRegex);
            if (ButtonClamped("Clear", GUI.skin.button))
            {
                filterRegex = "";
                GUIUtility.keyboardControl = 0;
                GUIUtility.hotControl = 0;
            }

            if (filterRegex != _filterRegex)
            {
                ClearSelectedMessage();
                _filterRegex = filterRegex;
            }
            GUILayout.EndHorizontal();
        }

        private void DrawChannels()
        {
            var channels = GetChannels();
            int currentChannelIndex = 0;
            for (int i = 0; i < channels.Count; i++)
            {
                if (channels[i] == _currentChannel)
                {
                    currentChannelIndex = i;
                    break;
                }
            }

            currentChannelIndex = GUILayout.SelectionGrid(currentChannelIndex, channels.ToArray(), channels.Count);
            if (_currentChannel != channels[currentChannelIndex])
            {
                _currentChannel = channels[currentChannelIndex];
                ClearSelectedMessage();
            }
        }

        private void DrawLogList()
        {
            var oldColor = GUI.backgroundColor;

            _logListScrollPosition = GUILayout.BeginScrollView(_logListScrollPosition, GetGUISkin(GUI.skin.label, Color.white, TextAnchor.UpperCenter));
            var maxLogPanelHeight = _windowRect.height;

            float buttonY = 0;
            float buttonHeight = _logLineStyle1.CalcSize(new GUIContent("Test")).y;

            Regex filterRegex = null;
            if (!string.IsNullOrEmpty(_filterRegex))
            {
                filterRegex = new Regex(_filterRegex);
            }

            int drawnButtons = 0;
            var logLineStyle = _logLineStyle1;
            for (int i = _logInfoList.Count - 1; i >= 0; i--)
            {
                var log = _logInfoList[i];
                if (ShouldShowLog(filterRegex, log))
                {
                    drawnButtons++;
                    if (buttonY + buttonHeight > _logListScrollPosition.y && buttonY < _logListScrollPosition.y + maxLogPanelHeight)
                    {
                        if (i == _selectedMessage)
                        {
                            logLineStyle = _selectedLogLineStyle;
                        }
                        else
                        {
                            logLineStyle = (drawnButtons % 2 == 0) ? _logLineStyle1 : _logLineStyle2;
                        }

                        var showMessage = log.Message;

                        showMessage = showMessage.Replace(Debugger.UnityInternalNewLine, " ");
                        if (_showTimes)
                        {
                            showMessage = log.RelativeTimeStampAsString + ": " + showMessage;
                        }

                        var content = new GUIContent(showMessage, GetIconForLog(log));
                        if (GUILayout.Button(content, logLineStyle, GUILayout.Height(buttonHeight)))
                        {
                            // 双击跳转到消息触发源
                            if (i == _selectedMessage)
                            {
                                if (Time.realtimeSinceStartup - _lastMessageClickTime < _mouseClickTime)
                                {
                                    _lastMessageClickTime = 0;
                                }
                                else
                                {
                                    _lastMessageClickTime = Time.realtimeSinceStartup;
                                }
                            }
                            else
                            {
                                _selectedMessage = i;
                                _selectedCallstackFrame = -1;
                            }
                        }
                    }
                    else
                    {
                        GUILayout.Space(buttonHeight);
                    }
                    buttonY += buttonHeight;
                }
            }

            GUILayout.EndScrollView();
            GUI.backgroundColor = oldColor;
        }

        public void DrawLogStackTrace()
        {
            var oldColor = GUI.backgroundColor;
            _selectedMessage = Mathf.Clamp(_selectedMessage, 0, _logInfoList.Count);
            if (_logInfoList.Count > 0 && _selectedMessage >= 0)
            {
                _logDetailsScrollPosition = GUILayout.BeginScrollView(_logDetailsScrollPosition, GetGUISkin(GUI.skin.label, Color.white, TextAnchor.LowerCenter), GUILayout.Height(128));
                var log = _logInfoList[_selectedMessage];
                var logLineStyle = _logLineStyle1;
                for (int i = 0; i < log.Callstack.Count; i++)
                {
                    var frame = log.Callstack[i];
                    var methodName = frame.FormattedMethodNameWithFileName;
                    if (!string.IsNullOrEmpty(methodName))
                    {
                        if (i == _selectedCallstackFrame)
                        {
                            logLineStyle = _selectedLogLineStyle;
                        }
                        else
                        {
                            logLineStyle = (i % 2 == 0) ? _logLineStyle1 : _logLineStyle2;
                        }
                        
                        if (GUILayout.Button(methodName, logLineStyle))
                        {
                            _selectedCallstackFrame = i;
                        }
                    }

                }
                GUILayout.EndScrollView();
            }
            GUI.backgroundColor = oldColor;
        }

        private void DrawActivationButton()
        {
            Texture2D buttonTex = _deleteButton;
            var buttonPos = new Vector2(4, 4);
            Vector2 ButtonSize = new Vector2(16, 16);
            //buttonPos.x *= Screen.width;
            //buttonPos.y *= Screen.height;
            //if (buttonPos.x + ButtonSize.x > Screen.width)
            //{
            //    buttonPos.x = Screen.width - ButtonSize.x;
            //}
            //if (buttonPos.y + ButtonSize.y > Screen.height)
            //{
            //    buttonPos.y = Screen.height - ButtonSize.y;
            //}
            var buttonRect = new Rect(buttonPos.x, buttonPos.y, ButtonSize.x, ButtonSize.y);
            var style = new GUIStyle();

            if (GUI.Button(buttonRect, buttonTex, style))
            {
                _showWindow = false;
            }
        }

        private List<string> GetChannels()
        {
            var categories = new HashSet<string>();
            foreach (var logInfo in _logInfoList)
            {
                if (!string.IsNullOrEmpty(logInfo.Channel) && !categories.Contains(logInfo.Channel))
                {
                    categories.Add(logInfo.Channel);
                }
            }

            var channelList = new List<string>();
            channelList.Add("All");
            channelList.Add("No Channel");
            channelList.AddRange(categories);
            return channelList;
        }

        private bool ShouldShowLog(Regex regex, LogInfo log)
        {
            if (log.Channel == _currentChannel || _currentChannel == "All" || string.IsNullOrEmpty(_currentChannel) || (_currentChannel == "No Channel" && string.IsNullOrEmpty(log.Channel)))
            {
                if ((log.Type == LogType.Log && _showMessage)
                   || (log.Type == LogType.Warning && _showWarning)
                   || ((log.Type == LogType.Error || log.Type == LogType.Exception) && _showError))
                {
                    if (regex == null || regex.IsMatch(log.Message))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private void ClearSelectedMessage()
        {
            _selectedMessage = 0;
            _selectedCallstackFrame = -1;
        }

        private void TrimOverLimitLogs()
        {
            while (_logInfoList.Count > Debugger.MaxlogsToKeep)
            {
                LogInfo logInfo = _logInfoList[0];
                _logInfoList.Remove(logInfo);

                if (logInfo.Type == LogType.Error || logInfo.Type == LogType.Exception)
                    _NoErrors--;
                else if (logInfo.Type == LogType.Warning)
                    _NoWarnings--;
                else
                    _NoMessages--;
            }
        }

        private void Clear()
        {
            _logInfoList.Clear();
            _NoWarnings = 0;
            _NoErrors = 0;
            _NoMessages = 0;
        }

        private bool ButtonClamped(string text, GUIStyle style)
        {
            return GUILayout.Button(text, style, GUILayout.MaxWidth(style.CalcSize(new GUIContent(text)).x));
        }

        private bool ToggleClamped(bool state, string text, GUIStyle style)
        {
            return GUILayout.Toggle(state, text, style, GUILayout.MaxWidth(style.CalcSize(new GUIContent(text)).x));
        }

        private bool ToggleClamped(bool state, GUIContent content, GUIStyle style, params GUILayoutOption[] par)
        {
            return GUILayout.Toggle(state, content, style, GUILayout.MaxWidth(style.CalcSize(content).x));
        }

        private void LabelClamped(string text, GUIStyle style)
        {
            GUILayout.Label(text, style, GUILayout.MaxWidth(style.CalcSize(new GUIContent(text)).x));
        }

        private Texture2D GetIconForLog(LogInfo log)
        {
            if (log.Type == LogType.Error || log.Type == LogType.Exception)
            {
                return _smallErrorIcon;
            }
            else if (log.Type == LogType.Warning)
            {
                return _smallWarningIcon;
            }
            else
            {
                return _smallMessageIcon;
            }
        }

        private GUIStyle GetGUISkin(GUIStyle guiStyle, Color color, TextAnchor style, int fontSize = -1)
        {
            guiStyle.normal.textColor = color;
            guiStyle.hover.textColor = color;
            guiStyle.active.textColor = color;
            guiStyle.onNormal.textColor = color;
            guiStyle.onHover.textColor = color;
            guiStyle.onActive.textColor = color;
            guiStyle.padding = new RectOffset(8, 8, 3, 3);
            guiStyle.alignment = style;

            if (fontSize < 0) guiStyle.fontSize = DefaultFontSize;
            else guiStyle.fontSize = fontSize;

            return guiStyle;
        }
        
        private GUIStyle GUISkin_LogWindow()
        {
            GUIStyle guiStyle = new GUIStyle();
            guiStyle.normal.background = _windowTexture;
            guiStyle.onNormal.background = _windowTexture;
            guiStyle.hover.background = _windowTexture;
            guiStyle.onHover.background = _windowTexture;
            guiStyle.active.background = _windowTexture;
            guiStyle.onActive.background = _windowTexture;
            guiStyle.focused.background = _windowTexture;
            guiStyle.onFocused.background = _windowTexture;
            guiStyle.border = new RectOffset(2, 2, 25, 2);
            guiStyle.padding = new RectOffset(10, 10, 26, 10);
            guiStyle.alignment = TextAnchor.UpperCenter;
            guiStyle.imagePosition = ImagePosition.ImageLeft;
            guiStyle.contentOffset = new Vector2(0, -24);
            guiStyle.richText = true;
            guiStyle.stretchWidth = true;
            guiStyle.stretchHeight = false;
            guiStyle.fontSize = DefaultFontSize;

            return guiStyle;
        }

        private GUIStyle GUISkin_Toolbar()
        {
            GUIStyle guiStyle = new GUIStyle();
            guiStyle.normal.background = _toolbarButton;
            guiStyle.normal.textColor = Color.white;
            guiStyle.onNormal.background = _toolbarButtonDown;
            guiStyle.onNormal.textColor = Color.white;
            guiStyle.hover.background = _toolbarButton;
            guiStyle.hover.textColor = Color.white;
            //guiStyle.onHover.background = _toolbarButton;
            guiStyle.onHover.textColor = Color.white;
            guiStyle.active.background = _toolbarButtonClicked;
            guiStyle.active.textColor = Color.white;
            guiStyle.onActive.background = _toolbarButtonClicked;
            guiStyle.onActive.textColor = Color.white;
            guiStyle.focused.background = _toolbarButton;
            guiStyle.focused.textColor = Color.white;
            guiStyle.onFocused.background = _logLineSelected;
            guiStyle.onFocused.textColor = Color.white;
            guiStyle.border = new RectOffset(1, 1, 0, 0);
            //guiStyle.margin = new RectOffset(1, 1, 0, 0);
            guiStyle.padding = new RectOffset(8, 8, 3, 3);
            guiStyle.alignment = TextAnchor.MiddleCenter;
            guiStyle.richText = true;
            guiStyle.stretchWidth = true;
            guiStyle.stretchHeight = false;
            guiStyle.fontSize = DefaultFontSize;

            return guiStyle;
        }

        private GUIStyle GUISkin_LogLine(Texture2D texture)
        {
            GUIStyle guiStyle = new GUIStyle();
            guiStyle.normal.background = texture;
            guiStyle.normal.textColor = Color.white;
            guiStyle.onNormal.background = texture;
            guiStyle.onNormal.textColor = Color.black;
            guiStyle.hover.background = texture;
            guiStyle.hover.textColor = Color.white;
            guiStyle.onHover.background = texture;
            guiStyle.onHover.textColor = Color.black;
            guiStyle.active.background = texture;
            guiStyle.active.textColor = Color.white;
            guiStyle.onActive.background = texture;
            guiStyle.onActive.textColor = Color.black;
            guiStyle.focused.background = texture;
            guiStyle.focused.textColor = Color.white;
            guiStyle.onFocused.background = texture;
            guiStyle.onFocused.textColor = Color.black;
            guiStyle.alignment = TextAnchor.MiddleLeft;
            guiStyle.imagePosition = ImagePosition.ImageLeft;
            guiStyle.richText = true;
            guiStyle.stretchWidth = true;
            guiStyle.stretchHeight = false;
            guiStyle.fontSize = DefaultFontSize - 2;

            return guiStyle;
        }
        
        /*private void LogWindow(int windowID)
        {
            if (GUI.Button(_rect_Clear, "Clear", GetGUISkin(GUI.skin.button, Color.white, TextAnchor.MiddleCenter)))
            {
                logInfos.Clear();
                _stackInfo = null;
                _command = string.Empty;
            }
            if (GUI.Button(_rect_Close, "Close", GetGUISkin(GUI.skin.button, Color.white, TextAnchor.MiddleCenter)))
            {
                _debugDisplay = DebugDisplay.Button;
                _command = string.Empty;
            }

            _isLogOn = GUI.Toggle(_rect_Log, _isLogOn, "Log", GetGUISkin(GUI.skin.toggle, Color.white, TextAnchor.MiddleLeft, Debugger.FontSize / 2));
            _isWarnningOn = GUI.Toggle(_rect_Warning, _isWarnningOn, "Warnning", GetGUISkin(GUI.skin.toggle, Color.yellow, TextAnchor.MiddleLeft, Debugger.FontSize / 2));
            _isErrorOn = GUI.Toggle(_rect_Error, _isErrorOn, "Error", GetGUISkin(GUI.skin.toggle, Color.red, TextAnchor.MiddleLeft, Debugger.FontSize / 2));

            GUILayout.Space((Screen.height * _buttonHeight) - 2f);

            _scrollLogPosition = GUILayout.BeginScrollView(_scrollLogPosition, new GUILayoutOption[0]);
            LogItem();
            GUILayout.EndScrollView();
        }

        private void StackWindow(int windowID)
        {
            _scrollStackPosition = GUILayout.BeginScrollView(_scrollStackPosition, new GUILayoutOption[0]);
            StackItem();
            GUILayout.EndScrollView();
        }

        private void StackItem()
        {
            if (_stackInfo != null)
            {
                GUILayout.Space(10f);

                char[] separator = new char[] { '\n' };
                string[] strArray = _stackInfo.StackTrace.Split(separator);
                GUILayout.Label(_stackInfo.Condition, GetGUISkin(GUI.skin.label, GetColor(_stackInfo.Type), TextAnchor.MiddleLeft), new GUILayoutOption[0]);
                for (int i = 0; i < strArray.Length; i++)
                {
                    GUILayout.Label(strArray[i], GetGUISkin(GUI.skin.label, GetColor(_stackInfo.Type), TextAnchor.MiddleLeft), new GUILayoutOption[0]);
                }
            }
        }

        private void LogItem()
        {
            for (int i = logInfos.Count - 1; i >= 0; i--)
            {
                if (logInfos[i].Type == LogType.Log)
                {
                    if (_isLogOn)
                    {
                        if (GUILayout.Button(logInfos[i].Condition, GetGUISkin(GUI.skin.button, GetColor(logInfos[i].Type), TextAnchor.MiddleLeft), new GUILayoutOption[0]))
                        {
                            _stackInfo = logInfos[i];
                        }
                    }
                    continue;
                }
                if (logInfos[i].Type == LogType.Warning)
                {
                    if (_isWarnningOn)
                    {
                        if (GUILayout.Button(logInfos[i].Condition, GetGUISkin(GUI.skin.button, GetColor(logInfos[i].Type), TextAnchor.MiddleLeft), new GUILayoutOption[0]))
                        {
                            _stackInfo = logInfos[i];
                        }
                    }
                    continue;
                }
                if ((logInfos[i].Type == LogType.Error) && !this._isErrorOn)
                {
                    continue;
                }
            }
        }
        */

        private enum DebugMouse
        {
            Up,
            Down
        }
    }
}
